//
//  NetworkManager.swift
//  TSKit
//
//  Created by chenyusen on 2018/2/6.
//  Copyright © 2018年 TechSen. All rights reserved.
//

import Foundation
import Moya
import SwiftyJSON


public class NetworkManager {
    private class APICache {
        static let shared = APICache()
        
        /// 所有存储和删除操作,都将放在此串行队列实现
        public var ioQueue: DispatchQueue
        
        /// 操作缓存用到的文件管理器
        public var fileManager: FileManager
        
        /// 磁盘缓存路径
        private var diskCachePath: String = {
            let dstPath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first!
            return (dstPath as NSString).appendingPathComponent("tsapi")
        }()
        
        private init() {
            ioQueue = DispatchQueue(label: "com.techsen.TSKit.apiCache.ioQueue")
            fileManager = ioQueue.sync { FileManager() }
        }
        
        /// 获取指定Key值缓存对应的缓存路径
        ///
        /// - Parameter key: key值
        /// - Returns: 对应的缓存路径
        private func cachePath(forComputedKey key: String) -> String {
            return (diskCachePath as NSString).appendingPathComponent(key)
        }
        
        
        /// 存储缓存数据
        ///
        /// - Parameters:
        ///   - cache: 缓存数据
        ///   - endPoint: 缓存数据对应的endPoint
        ///   - completion: 存储缓存完成回调
        public func store(_ cache: Data,
                          with endPoint: Endpoint,
                          completion: ((Bool) -> ())? = nil) {
            ioQueue.async {
                if !self.fileManager.fileExists(atPath: self.diskCachePath) {
                    do {
                        try self.fileManager.createDirectory(atPath: self.diskCachePath, withIntermediateDirectories: true, attributes: nil)
                    } catch _ {}
                }
                
                let computedKey = self.key(endPoint)
                
                let result = self.fileManager.createFile(atPath: self.cachePath(forComputedKey: computedKey), contents: cache, attributes: nil)
                
                DispatchQueue.main.async {
                    completion?(result)
                }
            }
        }
        
        
        /// 获取缓存数据
        ///
        /// - Parameter targetType: 缓存数据对应的TargetType
        /// - Returns: 获取的缓存数据
        public func cache(_ endPoint: Endpoint) -> Data? {
            let computedKey = key(endPoint)
            let filePath = cachePath(forComputedKey: computedKey)
            return (try? Data(contentsOf: URL(fileURLWithPath: filePath)))
        }
        
        
        public func clearCache(completion handler: (() -> ())? = nil) {
            ioQueue.async {
                do {
                    try self.fileManager.removeItem(atPath: self.diskCachePath)
                    try self.fileManager.createDirectory(atPath: self.diskCachePath, withIntermediateDirectories: true, attributes: nil)
                } catch _ { }
                
                if let handler = handler {
                    DispatchQueue.main.async {
                        handler()
                    }
                }
            }
        }
        
        
        /// 获取缓存大小
        ///
        /// - Returns: 缓存大小 单位字节
        public func cacheSize() -> UInt64 {
            var fileSize: UInt64 = 0
            ioQueue.async {
                if let fileEnumerator = self.fileManager.enumerator(atPath: self.diskCachePath) {
                    for fileName in fileEnumerator {
                        if let fileName = fileName as? String {
                            let filePath = self.cachePath(forComputedKey: fileName)
                            do {
                                let attr = try self.fileManager.attributesOfItem(atPath: filePath)
                                fileSize = attr[FileAttributeKey.size] as! UInt64
                                let dict = attr as NSDictionary
                                fileSize = dict.fileSize()
                            } catch {
                                print("Error: \(error)")
                            }
                        }
                    }
                }
            }
            return fileSize
        }
        
        
        /// 将endPoint转为一个md5的key
        ///
        /// - Parameter endPoint: endPoint
        /// - Returns: md5的key
        private func key(_ endPoint: Endpoint) -> String {
            return  "\(endPoint.hashValue)".md5
        }
    }
    static let `default` = NetworkManager()
    
    
    
    /// 创建与Alamofire交互的URLRequest时对其进行一些定制
    private static let requestClosure = { (endpoint: Endpoint, done: MoyaProvider.RequestResultClosure) in
        do {
            var urlRequest: URLRequest = try endpoint.urlRequest()
            urlRequest.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
            urlRequest.timeoutInterval = 10
            done(.success(urlRequest))
        } catch MoyaError.requestMapping(let url) {
            done(.failure(MoyaError.requestMapping(url)))
        } catch MoyaError.parameterEncoding(let error) {
            done(.failure(MoyaError.parameterEncoding(error)))
        } catch {
            done(.failure(MoyaError.underlying(error, nil)))
        }
    }
    
    fileprivate let provider: MoyaProvider<MultiTarget>!
    
    
    /// 有些项目通过code的值来指定具体响应请求的有效性,可以通过这个闭包处理
    static var customValidFilterClosure: ((JSON) -> Error?)?
    static var customEndpointClosure: MoyaProvider<MultiTarget>.EndpointClosure?
    static var customRequestClosure: MoyaProvider<MultiTarget>.RequestClosure?
    
    
    
    private init() {
        provider = MoyaProvider<MultiTarget>(endpointClosure: NetworkManager.customEndpointClosure ?? MoyaProvider.defaultEndpointMapping, requestClosure: NetworkManager.customRequestClosure ?? NetworkManager.requestClosure)
    }
    /// 网络请求
    ///
    /// - Parameters:
    ///   - targetType: 对应的TargetType
    ///   - count: 如果网络失败,尝试重复请求的次数,默认为0重试
    ///   - success: 成功回调
    ///   - failure: 失败回调
    /// - Returns: Cancellable对象,用于临时取消网络请求
    
    @discardableResult
    public func request(targetType: TargetType,
                                retry count: Int = 0,
                                success: ((JSON) -> ())? = nil,
                                failure: ((Error) -> ())? = nil) -> Cancellable {
        
        return provider.request(MultiTarget(targetType)) { [weak self] (result) in
                switch result {
                case let .success(response):
                    do {
                        let _ = try response.filterSuccessfulStatusCodes()
                        let json = try JSON(response.mapJSON())
                        if let error = NetworkManager.customValidFilterClosure?(json) {
                            failure?(error)
                        } else {
                            success?(json)
                        }
                    } catch {
                        failure?(error)
                    }
                case let .failure(error):
                    TSLog(error.errorDescription)
                   
                    // 如果是请求主动取消又或则无重新请求次数,则直接返回错误
                    if case let .underlying(error, _) = error, (error as NSError).code == NSURLErrorCancelled || count == 0 {
                        failure?(error)
                    } else {
                        self?.request(targetType: targetType, retry: count-1, success: success, failure: failure)
                    }
                }
        }
    }
}

extension NetworkManager {
    public func cache(_ targetType: TargetType) -> Data? {
        return APICache.shared.cache(provider.endpoint(MultiTarget(targetType)))
    }
    
    public func store(_ cache: Data,
                             with targetType: TargetType,
                             completion: ((Bool) -> ())? = nil) {
        APICache.shared.store(cache, with: provider.endpoint(MultiTarget(targetType)), completion: completion)
    }
}
